使用 pip 安裝
# 解析 HTML
pip install pyquery
以取得加權指數成分股暨市值比重資料為例。
https://www.taifex.com.tw/cht/9/futuresQADetail
class Propotion:
def __init__(self, sort, code, name, percent):
# 排序
self.Sort = sort
# 證券代碼
self.Code = code
# 證券名稱
self.Name = name
# 市值佔比
self.Percent = percent
使用 PyQuery 解析 HTML 內容,因 PyQuery 套件庫用法參考自 jQuery,故函數用法等均類同於 jQuery,對於多數開發者而言應較 Beautiful Soup 4 更加容易上手。
import datetime
import chardet
import loguru
import pyquery
import requests
def main():
resp = requests.get('https://www.taifex.com.tw/cht/9/futuresQADetail')
if resp.status_code != 200:
loguru.logger.error('RESP: status code is not 200')
loguru.logger.success('RESP: success')
txt = None
det = chardet.detect(resp.content)
try:
if det['confidence'] > 0.5:
if det['encoding'] == 'big-5':
txt = resp.content.decode('big5')
else:
txt = resp.content.decode(det['encoding'])
else:
txt = resp.content.decode('utf-8')
except Exception as e:
loguru.logger.error(e)
if txt is None:
return
loguru.logger.info(txt)
# 成分股市值佔比清單
proportions = []
# 將下載回來的內容解析為 PyQuery 物件
d = pyquery.PyQuery(txt)
# 透過 CSS 選擇器取出所有表格行
trs = list(d('table tr').items())
# 去除標頭行(分析結果 1.)
trs = trs[1:]
# 依序取出資料行
for tr in trs:
# 取出所有資料格
tds = list(tr('td').items())
#
# 取出資料行中第一組證券內容(分析結果 2.)
#
# 取出證券代碼欄位值
code = tds[1].text().strip()
# 若證券代碼欄位值存在資料,代表本筆資料存在,則繼續取出其他欄位
if code != '':
# 取出排序欄位值
sort = tds[1].text().strip()
# 取出證券名稱欄位值
name = tds[2].text().strip()
# 取出市值佔比欄位值
percent = tds[3].text().strip()
# 將取得資料存入成分股市值佔比清單
proportions.append(Propotion(
sort=sort,
code=code,
name=name,
percent=percent
))
#
# 取出資料行中第二組證券內容(分析結果 2.)
#
# 取出證券代碼欄位值
code = tds[5].text().strip()
# 若證券代碼欄位值存在資料,代表本筆資料存在,則繼續取出其他欄位
if code != '':
# 取出排序欄位值
sort = tds[5].text().strip()
# 取出證券名稱欄位值
name = tds[6].text().strip()
# 取出市值佔比欄位值
percent = tds[7].text().strip()
# 將取得資料存入成分股市值佔比清單
proportions.append(Propotion(
sort=sort,
code=code,
name=name,
percent=percent
))
# 按證券代碼順序重新排列資列並輸出(分析結果 3.)
proportions.sort(key=lambda proportion: proportion.Code)
loguru.logger.info(proportions)
if __name__ == '__main__':
loguru.logger.add(
f'{datetime.date.today():%Y%m%d}.log',
rotation='1 day',
retention='7 days',
level='DEBUG'
)
main()
因為存入資料都是字串,但實際內容有整數(排序),還有浮點數(市值佔比),所以需要在儲存類別的建構子中進行轉換處理。
import fractions
class Propotion:
# 建構子
def __init__(self, sort, code, name, percent):
# 排序
# 轉換為整數,以利後續排序使用
self.Sort = int(sort)
# 證券代碼
self.Code = code
# 證券名稱
self.Name = name
# 市值佔比
# 不建議使用 float 進行浮點數保存,後續可能會造成浮點數計算誤差,
# 使用 Fraction 保存才能有效降低浮點數計算結果的誤差
self.Percent = fractions.Fraction(percent[:-1])
# 物件表達式
def __repr__(self):
return (
f'class Propotion {{ '
f'Sort={self.Sort}, '
f'Code={self.Code}, '
f'Name={self.Name}, '
f'Percent={float(self.Percent):.4f}% '
f'}}'
)
在定義儲存物件的表達式後,我們會看到輸出如下圖:
雖然已經格式化,但卻有點雜亂難以閱讀,所以必須修正輸出方式。
import datetime
import fractions
import os
import chardet
import loguru
import pyquery
import requests
class Propotion:
def __init__(self, sort, code, name, percent):
self.Sort = int(sort)
self.Code = code
self.Name = name
self.Percent = fractions.Fraction(percent[:-1])
def __repr__(self):
return (
f'class Propotion {{ '
f'Sort={self.Sort}, '
f'Code={self.Code}, '
f'Name={self.Name}, '
f'Percent={float(self.Percent):.4f}% '
f'}}'
)
def main():
resp = requests.get('https://www.taifex.com.tw/cht/9/futuresQADetail')
if resp.status_code != 200:
loguru.logger.error('RESP: status code is not 200')
loguru.logger.success('RESP: success')
txt = None
det = chardet.detect(resp.content)
try:
if det['confidence'] > 0.5:
if det['encoding'] == 'big-5':
txt = resp.content.decode('big5')
else:
txt = resp.content.decode(det['encoding'])
else:
txt = resp.content.decode('utf-8')
except Exception as e:
loguru.logger.error(e)
if txt is None:
return
loguru.logger.info(txt)
proportions = []
d = pyquery.PyQuery(txt)
trs = list(d('table tr').items())
trs = trs[1:]
for tr in trs:
tds = list(tr('td').items())
code = tds[1].text().strip()
if code != '':
sort = tds[1].text().strip()
name = tds[2].text().strip()
percent = tds[3].text().strip()
proportions.append(Propotion(
sort=sort,
code=code,
name=name,
percent=percent
))
code = tds[5].text().strip()
if code != '':
sort = tds[5].text().strip()
name = tds[6].text().strip()
percent = tds[7].text().strip()
proportions.append(Propotion(
sort=sort,
code=code,
name=name,
percent=percent
))
proportions.sort(key=lambda proportion: proportion.Code)
loguru.logger.info(proportions)
# 將每筆物件表達式輸出的字串以系統換行符號相接,讓每筆物件表達式各自獨立一行
message = os.linesep.join([str(proportion) for proportion in proportions])
loguru.logger.info('PROPORTIONS' + os.linesep + message)
if __name__ == '__main__':
loguru.logger.add(
f'{datetime.date.today():%Y%m%d}.log',
rotation='1 day',
retention='7 days',
level='DEBUG'
)
main()
重新執行得到適合人類閱讀的輸出結果。
https://pythonhosted.org/pyquery/
團隊系列文:
CSScoke - 金魚都能懂的這個網頁畫面怎麼切 - 金魚都能懂了你還怕學不會嗎
Clarence - LINE bot 好好玩 30 天玩轉 LINE API
Hina Hina - 陣列大亂鬥
King Tzeng - IoT沒那麼難!新手用JavaScript入門做自己的玩具
Vita Ora - 好 Js 不學嗎 !? JavaScript 入門中的入門。
TaTaMo - 用Python開發的網頁不能放到Github上?Lektor說可以!!